//===- FileSystem.inc -----------------------------------------------------===//
//
//                     The MCLinker Project
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "mcld/Support/FileHandle.h"
#include "mcld/Support/Directory.h"

#include <llvm/Support/ErrorHandling.h>

#include <string>

#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>

namespace mcld {
namespace sys {
namespace fs {
namespace detail {

std::string static_library_extension = ".a";
std::string shared_library_extension = ".so";
std::string executable_extension = "";
std::string relocatable_extension = ".o";
std::string assembly_extension = ".s";
std::string bitcode_extension = ".bc";

//===----------------------------------------------------------------------===//
// Helper Functions
//===----------------------------------------------------------------------===//
/// read_dir - return true if we read one entry
//  @return value -1: read error
//                 0: read the end
//                 1: success
static int read_dir(intptr_t& pDir, std::string& pOutFilename) {
  errno = 0;
  dirent* cur_dir = ::readdir(reinterpret_cast<DIR*>(pDir));
  if (0 == cur_dir && 0 != errno)
    return -1;

  // idx does not stay at the end, but all elements had beed put into cache.
  if (NULL == cur_dir) {
    return 0;
  }

  llvm::StringRef name(cur_dir->d_name, strlen(cur_dir->d_name));
  if ((name.size() == 1 && name[0] == '.') ||
      (name.size() == 2 && name[0] == '.' && name[1] == '.'))
    return read_dir(pDir, pOutFilename);

  // find a new directory
  pOutFilename.append(name.data(), name.size());
  return 1;
}

void open_dir(Directory& pDir) {
  pDir.m_Handler = reinterpret_cast<intptr_t>(opendir(pDir.path().c_str()));
  if (0 == pDir.m_Handler) {
    errno = 0;  // opendir() will set errno if it failed to open directory.
    // set cache is full, then Directory::begin() can return end().
    pDir.m_CacheFull = true;
    return;
  }
  // read one entry for advance the end element of the cache.
  std::string path(pDir.path().native());
  switch (read_dir(pDir.m_Handler, path)) {
    case 1: {
      // find a new directory
      bool exist = false;
      mcld::sys::fs::PathCache::entry_type* entry =
          pDir.m_Cache.insert(path, exist);
      if (!exist)
        entry->setValue(sys::fs::Path(path));
      return;
    }
    case 0:
      // FIXME: a warning function
      pDir.m_CacheFull = true;
      return;
    default:
    case -1:
      llvm::report_fatal_error(std::string("Can't read directory: ") +
                               pDir.path().native());
  }
}

void close_dir(Directory& pDir) {
  if (pDir.m_Handler)
    closedir(reinterpret_cast<DIR*>(pDir.m_Handler));
  pDir.m_Handler = 0;
}

int open(const Path& pPath, int pOFlag) {
  return ::open(pPath.native().c_str(), pOFlag);
}

int open(const Path& pPath, int pOFlag, int pPerm) {
  mode_t perm = 0;
  if (pPerm & FileHandle::ReadOwner)
    perm |= S_IRUSR;
  if (pPerm & FileHandle::WriteOwner)
    perm |= S_IWUSR;
  if (pPerm & FileHandle::ExeOwner)
    perm |= S_IXUSR;
  if (pPerm & FileHandle::ReadGroup)
    perm |= S_IRGRP;
  if (pPerm & FileHandle::WriteGroup)
    perm |= S_IWGRP;
  if (pPerm & FileHandle::ExeGroup)
    perm |= S_IXGRP;
  if (pPerm & FileHandle::ReadOther)
    perm |= S_IROTH;
  if (pPerm & FileHandle::WriteOther)
    perm |= S_IWOTH;
  if (pPerm & FileHandle::ExeOther)
    perm |= S_IXOTH;

  return ::open(pPath.native().c_str(), pOFlag, perm);
}

ssize_t pread(int pFD, void* pBuf, size_t pCount, off_t pOffset) {
  return ::pread(pFD, pBuf, pCount, pOffset);
}

ssize_t pwrite(int pFD, const void* pBuf, size_t pCount, off_t pOffset) {
  return ::pwrite(pFD, pBuf, pCount, pOffset);
}

int ftruncate(int pFD, size_t pLength) {
  return ::ftruncate(pFD, pLength);
}

void get_pwd(Path& pPWD) {
  char* pwd = (char*)malloc(PATH_MAX);
  pPWD.assign(getcwd(pwd, PATH_MAX));
  free(pwd);
}

}  // namespace detail
}  // namespace fs
}  // namespace sys

//===----------------------------------------------------------------------===//
// FileHandler
//===----------------------------------------------------------------------===//
bool FileHandle::mmap(void*& pMemBuffer, size_t pStartOffset, size_t pLength) {
  if (!isOpened()) {
    setState(BadBit);
    return false;
  }

  if (0 == pLength)
    return true;

  int prot, flag;
  if (isReadable() && !isWritable()) {
    // read-only
    prot = PROT_READ;
    flag = MAP_FILE | MAP_PRIVATE;
  } else if (!isReadable() && isWritable()) {
    // write-only
    prot = PROT_WRITE;
    flag = MAP_FILE | MAP_SHARED;
  } else if (isReadWrite()) {
    // read and write
    prot = PROT_READ | PROT_WRITE;
    flag = MAP_FILE | MAP_SHARED;
  } else {
    // can not read/write
    setState(BadBit);
    return false;
  }

  pMemBuffer = ::mmap(NULL, pLength, prot, flag, m_Handler, pStartOffset);

  if (MAP_FAILED == pMemBuffer) {
    setState(FailBit);
    return false;
  }

  return true;
}

bool FileHandle::munmap(void* pMemBuffer, size_t pLength) {
  if (!isOpened()) {
    setState(BadBit);
    return false;
  }

  if (-1 == ::munmap(pMemBuffer, pLength)) {
    setState(FailBit);
    return false;
  }

  return true;
}

}  // namespace mcld
